﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SharePoint;

namespace SharePoint.Orm.SiteBuilders {
	///<summary>
	/// EntityBuilderBase provides the template for building an entity in a SharePoint site.
	///</summary>
	/// <example>
	/// <code>
	/// public class ScoreBuilder : EntityBuilderBase{
	///public ScoreBuilder (BuilderContext ctx) : base(ctx) {
	///     GroupName = "ScoreGroup";
	///}
	///protected override ContentTypeBuilder GetContentTypeBuilder() {
	///    BuildingContainer lookupBuilder = (BuildingContainer) BuilderContext.BuildingClassContainer;
	///    SPFieldLookup userLookup = lookupBuilder.UserBuilder.FindAddLookupField();
	///    return new ContentTypeBuilder {
	///    Name = "Player Score",
	///    Description = "Score in a game",
	///    FieldLinks = new List<FieldLinkBuilder> {
	///        new FieldLinkBuilder{Field = userLookup}
	///    }
	///    };
	///}
	///protected override ListBuilder GetListBuilder() {
	///    return new ListBuilder { Name = "Scores", Description = "A list of scores", OnQuickLaunch = false, Hidden = true };
	///}
	///protected override IEnumerable<ViewBuilder> GetViewBuilders() {
	///    return new[] {new ViewBuilder {
	///        ViewName = "All Scores",
	///        MakeViewDefault = true,
	///        ViewFields = new []{EDIT_FIELD}
	///    }};
	///}
	/// </code>
	/// </example>
	public abstract class EntityBuilderBase {
		protected EntityBuilderBase(BuilderContext builderContext) {
			BuilderContext = builderContext;
		}

		protected BuilderContext BuilderContext { get; private set; }

		///<summary>
		///</summary>
		public SPContentType ContentType { get; protected set; }
		/// <summary>
		/// </summary>
		public IList<SPContentType> ContentTypes { get; protected set; }
		///<summary>
		///</summary>
		public SPList List { get; protected set; }


		protected abstract ListBuilder GetListBuilder();
		protected virtual IEnumerable<ViewBuilder> GetViewBuilders() {
			return Enumerable.Empty<ViewBuilder>();
		}
		protected string GroupName { get; set; }
		[Obsolete("Use ChangeContentTypesAfterCreation instead.")]
		protected virtual void UpdateContentTypes(IList<SPContentType> contentType) { }
		[Obsolete("Use ChangeListAfterCreation instead.")]
		protected virtual void UpdateList(SPList list) { }
		[Obsolete("Doesn't do anything")]
		protected virtual void UpdateViews(IEnumerable<SPView> views) { }
		protected virtual void ChangeListAfterCreation(SPList list) { }
		protected virtual void ChangeContentTypesAfterCreation(IList<SPContentType> contentType) { }
		protected virtual ContentTypeBuilder GetContentTypeBuilder() {
			return null;
		}
		protected virtual IList<ContentTypeBuilder> GetContentTypeBuilders() {
			return new List<ContentTypeBuilder>();
		}
		protected virtual void PopulateProductionData() {
		}

		protected IFieldBuilder SiteFieldBuilder {
			get { return BuilderContext.SiteFieldBuilder; }
		}

		protected SPDataContext Ctx {
			get { return BuilderContext.Ctx; }
		}

		protected virtual SPContentTypeId? ContentTypeId {
			get { return null; }
		}

		protected const string TITLE_FIELD = "Title";
		protected const string LINK_TITLE_FIELD = "LinkTitle";
		protected const string EDIT_FIELD = "Edit";
		protected const string AUTHOR_FIELD = "Author";
		protected const string ORDERBY_MODIFIED_CAML = @"<OrderBy><FieldRef Name=""Modified"" /></OrderBy>";

		///<summary>
		/// Builds the current entity.
		///</summary>
		///<exception cref="ArgumentException">The builder context must be setup before this method is called.</exception>
		public void Build() {

			if (BuilderContext.Ctx == null) throw new ArgumentException("Ctx must be set prior to calling Build.");
			if (BuilderContext.SiteFieldBuilder == null) throw new ArgumentException("SiteFieldBuilder must be set prior to calling Build.");

			InitializeBuilders();

			ContentTypes = FindAddContentTypes();
			UpdateContentTypes(ContentTypes);
			ChangeContentTypesAfterCreation(ContentTypes);
			List = FindAddList();
			UpdateList(List);
			ChangeListAfterCreation(List);
			FindAddViews();
			BuilderContext.RefreshContext();
			PopulateProductionData();
		}

		private void InitializeBuilders() {
			if (ContentTypeBuilder.Site == null) {
				ContentTypeBuilder.Site = Ctx.Site;
				ContentTypeBuilder.ContentTypeGroupName = GroupName;
			}
			if (ListBuilder.Site == null)
				ListBuilder.Site = Ctx.Site;
			if (FieldLookupBuilder.Site == null) {
				FieldLookupBuilder.Site = Ctx.Site;
				FieldLookupBuilder.GroupName = GroupName;
			}
		}

		private void FindAddViews() {
			foreach (ViewBuilder viewBuilder in GetViewBuilders()) {
				viewBuilder.List = List;
				viewBuilder.FindAdd();
			}
		}

		private IList<SPContentType> FindAddContentTypes() {
			SPContentType singleContentType = GetContentTypeBuilderContentType();
			IEnumerable<SPContentType> multipleContentTypes = GetContentTypeBuildersContentType();
			SPContentType contentTypeId = GetContentTypeIdContentType();
			var singleContentTypes = new[] { singleContentType, contentTypeId };
			var contentTypes = multipleContentTypes.Concat(singleContentTypes);

			return contentTypes.Where(ct => ct != null).ToList();
		}
		private IEnumerable<SPContentType> GetContentTypeBuildersContentType() {
			IList<ContentTypeBuilder> contentTypeBuilders = GetContentTypeBuilders();
			IList<SPContentType> contentTypes = contentTypeBuilders.Select(ctb => ctb.FindAdd()).ToList();
			return contentTypes;
		}

		private SPContentType GetContentTypeIdContentType() {
			return ContentTypeId == null ? null : BuilderContext.Ctx.Site.Site.RootWeb.ContentTypes[ContentTypeId.Value];
		}
		private SPContentType GetContentTypeBuilderContentType() {
			ContentTypeBuilder contentTypeBuilder = GetContentTypeBuilder();
			return contentTypeBuilder == null ? null : contentTypeBuilder.FindAdd();
		}
		private SPList FindAddList() {
			ListBuilder listBuilder = GetListBuilder();
			listBuilder.ContentTypes = ContentTypes;
			return listBuilder.FindAdd();
		}

		///<summary>
		/// Cleans up the used resources by the Builder.
		///</summary>
		public static void CleanUpStaticResources() {
			ListBuilder.Site = null;
			FieldBuilder.Site = null;
			FieldLookupBuilder.Site = null;
			ContentTypeBuilder.Site = null;
		}

		private SPFieldLookup _fieldLookup;

		protected virtual FieldLookupBuilder GetFieldLookupBuilder() {
			throw new NotImplementedException("You must implement GetFieldLookupBuilder for " + GetType().Name);
		}

		///<summary>
		/// Finds and/or adds the lookup field of an entity.
		///</summary>
		///<returns>The lookup field for the inherited entity.</returns>
		///<exception cref="ArgumentException"></exception>
		public virtual SPFieldLookup FindAddLookupField() {
			if (_fieldLookup == null) {
				FieldLookupBuilder fieldLookupBuilder = GetFieldLookupBuilder();
				fieldLookupBuilder.List = List;
				if (List == null) {
					throw new ArgumentException("The List property has not been set yet for '" + GetType().Name + "', probably because Build() has not been called for it yet, probably because the order of the property was wrong in BuilderContext.");
				}
				_fieldLookup = fieldLookupBuilder.FindAdd();
			}
			return _fieldLookup;
		}
	}
}
